<template>
  <div>
    <el-card>
      <CategorySelector @changeCategory="changeCategory" :isShowList="isShowList"></CategorySelector>
    </el-card>
    <el-card style="margin-top:20px">
      <!-- 列表页 -->
      <div v-show="isShowList">
        <el-button type="primary" icon="el-icon-plus" @click="showAddDiv" :disabled="!category3Id">添加属性</el-button>
        <el-table border style="width: 100%" :data="attrList">
          <el-table-column type="index" label="序号" width="80" align="center">
          </el-table-column>
          <el-table-column prop="attrName" label="属性名称" width="150">
          </el-table-column>
          <el-table-column label="属性值列表" width="width">
            <!-- 遍历每个属性当中的属性值列表 -->
            <template slot-scope="{row,$index}">
              <el-tag type="success" v-for="attrValue in row.attrValueList" :key="attrValue.id" style="margin-right: 5px">
                {{ attrValue.valueName }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="操作" label="操作" width="150">
            <template slot-scope="{row,$index}">
              <HintButton type="warning" icon="el-icon-edit" size="mini" title="修改" @click="showUpdateDiv(row)" style="margin-right: 5px"></HintButton>
              <el-popconfirm :title="`确认删除${row.attrName}吗？`" @onConfirm="deleteAttr(row)">
                <HintButton slot="reference" type="danger" icon="el-icon-delete" size="mini" title="删除"></HintButton>
              </el-popconfirm>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <!-- 添加或编辑页面 -->
      <div v-show="!isShowList">
        <el-form :model="attrForm" :inline="true">
          <el-form-item label="属性名">
            <el-input v-model="attrForm.attrName" placeholder="请输入属性名"></el-input>
          </el-form-item>
        </el-form>
        <el-button type="primary" icon="el-icon-plus" :disabled="!attrForm.attrName" @click="addAttrValue">添加属性值</el-button>
        <el-button @click="isShowList = true">取消</el-button>
        <el-table :data="attrForm.attrValueList" style="width: 100%; margin: 20px 0" border>
          <el-table-column type="index" align="center" label="序号" width="80">
          </el-table-column>
          <el-table-column prop="prop" label="属性值名称" width="width">
            <template slot-scope="{row,$index}">
              <el-input :ref="$index" size="mini" v-if="row.isEdit" v-model="row.valueName" placeholder="请输入属性值名称" @blur="toLook(row)" @keyup.enter.native="toLook(row)"></el-input>
              <!-- span 是行内元素,宽高由内容撑开,导致点击区域过小,所以可以将 span 变为块级元素,并设置宽高 -->
              <span v-else @click="toEdit(row,$index)" style="display: block;width: 100%;height: 100%;">{{ row.valueName }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="prop" label="操作" width="width">
            <template slot-scope="{row,$index}">
              <!-- 气泡确认框点击确定按钮的事件名称叫 onConfirm 不是 confirm (官网写错了) -->
              <el-popconfirm :title="`确定要删除${row.valueName}吗？`" @onConfirm="attrForm.attrValueList.splice($index,1)">
                <HintButton slot="reference" type="danger" icon="el-icon-delete" size="mini" title="删除"></HintButton>
              </el-popconfirm>
            </template>
          </el-table-column>
        </el-table>
        <el-button type="primary" @click="save" :disabled="attrForm.attrValueList.length === 0">保存</el-button>
        <el-button @click="isShowList = true">取消</el-button>
      </div>
    </el-card>
  </div>
</template>

<script>
// lodash 深拷贝
import cloneDeep from 'lodash/cloneDeep'

export default {
  name: 'Attr',
  data () {
    return {
      // 保存子组件传递过来的 id 发请求使用
      category1Id: '',
      category2Id: '',
      category3Id: '',
      attrList: [],
      // 控制第二个 card 显示添加还是列表
      isShowList: true,
      attrForm: {
        attrName: '',
        attrValueList: [],
        // categoryId: this.category3Id,  这样不行,在 data 当中不能使用 this
        // 三级分类 ID
        categoryId: 0,
        categoryLevel: 3
      }
    }
  },
  methods: {
    changeCategory ({ categoryId, level }) {
      if (level === 1) {
        this.category1Id = categoryId

        // 清空父组件数据
        this.category2Id = ''
        this.category3Id = ''
        this.attrList = []
      } else if (level === 2) {
        this.category2Id = categoryId

        // 清空父组件数据
        this.category3Id = ''
        this.attrList = []
      } else {
        this.category3Id = categoryId
        // 三级 id 被选中,发请求获取属性列表数据
        this.getAttrList()
      }
    },
    async getAttrList () {
      const { category1Id, category2Id, category3Id } = this
      const result = await this.$API.attr.getList(category1Id, category2Id, category3Id)
      if (result.code === 200) {
        this.attrList = result.data
      }
    },
    // 点击添加按钮切换到添加页面
    showAddDiv () {
      this.isShowList = false
      // 清空收集的对象,完成收集的对象剩余的收集操作
      this.attrForm = {
        attrName: '',
        attrValueList: [],
        categoryId: this.category3Id,
        categoryLevel: 3
      }
    },
    // 点击添加属性值按钮
    addAttrValue () {
      // 给属性值列表添加一个空的对象,保证表格当中出现一行
      this.attrForm.attrValueList.push({
        // 属性值所属属性的 id,如果是添加属性, id 值为 undefined
        attrId: this.attrForm.id,
        // 属性值让用户输入,输入了才有值
        valueName: '',
        // 添加属性值的时候,属性值对象标识数据是编辑模式
        isEdit: true
      })

      // 自动获取焦点的逻辑: 新添加属性值, input 要自动获取焦点,新添加的属性值是属性值列表当中的最后一项
      this.$nextTick(() => {
        this.$refs[this.attrForm.attrValueList.length - 1].focus()
      })
    },
    // 点击修改按钮切换到修改的页面
    showUpdateDiv (row) {
      this.isShowList = false
      // 由于 attrForm 中的 attrValueList 是一个对象,所以必须使用深拷贝
      this.attrForm = cloneDeep(row)

      // 修改属性时,每个属性值都添加一个自己的模式标识数据
      this.attrForm.attrValueList.forEach(item => {
        /*
           item.isEdit = false
           不能用上面的写法,因为这是在后期给响应式对象添加新的属性
           只有在数据初始化阶段,即 beforeCreate 和 created 之间初始化的对象,对象当中属性才是响应式的数据
           此时点击按钮给响应式对象添加新的属性数据,不是响应式的数据,这样会导致后期添加了以后页面不变化
           如果后期添加的属性要响应式,必须使用 Vue.set 或 vm.$set 才能让其成为响应式数据
        */
        this.$set(item, 'isEdit', false)
      })
    },
    // input 失去焦点或者回车之后切换为 span
    toLook (row) {
      // 校验用户输入的数据
      const { valueName } = row
      if (valueName.trim() === '') {
        this.$message.info('属性值名称不能为空')
        row.valueName = ''
        return
      }

      // 用户输入的数据不能和已有的其它属性值名称重复
      const isRepeat = this.attrForm.attrValueList.some(item => {
        // item 和 row 是同一个对象,所以才能比较
        return item !== row && item.valueName === row.valueName
      })
      if (isRepeat) {
        this.$message.info('属性值名称不能重复')
        row.valueName = ''
        return
      }

      row.isEdit = false
    },
    // span 点击之后切换为 input
    toEdit (row, index) {
      row.isEdit = true

      /*
        自动获取焦点,根据 index 可以知道是哪个 input 要获取焦点,并且该 index 刚好就是对应的 input 的 ref 值
        this.$refs[index] 值为 undefined 的原因
          因为获取这个节点太快
          当 row.isEdit = true 之后,由于 dom 元素是通过 v-if 才开始创建,紧接着就开始获取这个元素
          此时 input 元素还没创建完毕,所以获取到的就是 undefined
  
        $nextTick 代表在页面的最近一次更新完成之后执行回调

        this.$nextTick 和 updated 的区别
          this.$nextTick 页面的最近一次更新完成之后执行回调,只会执行一次
          updated        只要页面有数据更新就会执行
      */

      // 等待页面最近更新完成之后再去获取 input 进行获取焦点
      this.$nextTick(() => {
        this.$refs[index].focus()
      })
    },
    // 保存
    async save () {
      let attr = this.attrForm
      // 整理参数
      attr.attrValueList = attr.attrValueList.filter(item => {
        if (item.valueName !== '') {
          // 删除不需要的属性值
          delete item.isEdit
          return true
        }
      })
      if (attr.attrValueList.length === 0) {
        this.$message.warning('属性必须有属性值')
        return
      }
      // 发送请求
      try {
        await this.$API.attr.addOrUpdate(attr)
        this.$message.success('保存成功')
        this.getAttrList()
        this.isShowList = true
      } catch (error) {
        this.$message.error('保存失败')
      }
    },
    // 删除
    async deleteAttr (row) {
      try {
        await this.$API.attr.delete(row.id)
        this.$message.success('删除成功')
        this.getAttrList()
      } catch (error) {
        this.$message.error('删除失败')
      }
    }
  }
}
</script>

<style lange="scss" scoped>
</style>